package ss.pku.jsa.type.test;

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.equinox.app.IApplication;
import org.eclipse.equinox.app.IApplicationContext;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IOpenable;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;

import ss.pku.jsa.type.inference.Analysis;
import ss.pku.jsa.type.inference.JqualPlugin;
import ss.pku.jsa.type.inference.MethodQType;
import ss.pku.jsa.type.io.AnnotationStore;
import ss.pku.jsa.type.io.AnnotationWriter;
import ss.pku.jsa.type.io.FieldQSig;

@SuppressWarnings({"rawtypes","unchecked"})
public class Experiments implements IApplication {
	IWorkspaceRoot root = JqualPlugin.getWorkspace().getRoot();
	String rootPath = this.root.getLocation().toString() + "/";

	Analysis gizmoball() {
		return analyzeProj("javari_GB");
	}

	List<IPackageFragment> getPackages(IProject p) {
		IJavaProject jp = JavaCore.create(p);

		List ret = new Vector();
		try {
			IPackageFragmentRoot r = jp.findPackageFragmentRoot(jp.getPath());
			if (r.getElementName().contains(".jar")) {
				JqualPlugin.out.println("Ignoring jar file: "
						+ r.getElementName());
			} else
				for (IJavaElement c : r.getChildren()) {
					ret.add((IPackageFragment) c);
				}
		} catch (JavaModelException e) {
			throw new RuntimeException(e.toString());
		}
		return ret;
	}

	Analysis analyzeProj(String name) {
		IProject proj = this.root.getProject("/" + name);
		List folders = new LinkedList();
		for (IOpenable element : getPackages(proj)) {
			folders.add(element);
		}
		IOpenable[] inputs = (IOpenable[]) folders.toArray(new IOpenable[1]);
		return JqualPlugin.analyze(inputs, false);
	}

	Analysis jgraph() {
		return analyzeProj("jgraph");
	}

	Analysis jdbm() {
		return analyzeProj("jdbm-1.0");
	}

	Analysis hsqldb() {
		return analyzeProj("hsqldb");
	}

	Analysis pooka() {
		return analyzeProj("pooka");
	}

	Analysis jtds() {
		return analyzeProj("jtds-1.2");
	}

	Analysis jgap() {
		return analyzeProj("jgap-2.6");
	}

	Analysis jfreechart() {
		return analyzeProj("jfreechart-1.0.1");
	}

	Analysis ffi(String dir, boolean subtypes) {
		JqualPlugin.ffiAnnotationsPath = this.rootPath + dir
				+ "/annotations.txt";
		JqualPlugin.ffiQualsPath = this.rootPath + dir + "/qual_list.txt";

		JqualPlugin.out.println(JqualPlugin.ffiAnnotationsPath);
		JqualPlugin.out.println(JqualPlugin.ffiQualsPath);

		JqualPlugin.useClosure = false;
		JqualPlugin.useJavalib = false;

		JqualPlugin.inferOpaque = true;
		JqualPlugin.useFFISubtypes = subtypes;
		JqualPlugin.inferReadonly = false;

		List folders = new LinkedList();

		IProject proj = this.root.getProject("/" + dir);
		folders.add((IOpenable) JavaCore.create(proj.getFolder("/org/gnu/atk")));
		folders.add((IOpenable) JavaCore.create(proj.getFolder("org/gnu/pango")));
		folders.add((IOpenable) JavaCore.create(proj.getFolder("org/gnu/gdk")));
		folders.add((IOpenable) JavaCore.create(proj.getFolder("org/gnu/gtk")));
		folders.add((IOpenable) JavaCore.create(proj
				.getFolder("org/gnu/gtk/event")));
		folders.add((IOpenable) JavaCore.create(proj.getFolder("org/gnu/glib")));

		IOpenable[] inputs = (IOpenable[]) folders.toArray(new IOpenable[1]);
		Analysis analysis = JqualPlugin.analyze(inputs, false);

		JqualPlugin.out.println("ENUM: " + analysis.results);
		JqualPlugin.printlnLog("ffi_out/" + dir + ".out", analysis.toString());
		return analysis;
	}

	Analysis javalibs() {
		IOpenable[] inputs = { JavaCore
				.create(this.root.getProject("/javalib")) };
		Analysis analysis = JqualPlugin.analyze(inputs, false);
		return analysis;
	}

	Analysis spec(String projName) {
		IProject proj = this.root.getProject("/spec");
		List folders = new LinkedList();
		folders.add((IOpenable) JavaCore.create(proj.getFolder("/spec/harness")));
		folders.add((IOpenable) JavaCore.create(proj.getFolder("/spec/io")));
		folders.add((IOpenable) JavaCore.create(proj
				.getFolder("/spec/reporter")));
		folders.add((IOpenable) JavaCore.create(proj
				.getFile("BasePeakGraph.java")));
		folders.add((IOpenable) JavaCore.create(proj.getFile("Graph.java")));

		folders.add((IOpenable) JavaCore.create(proj
				.getFolder("/spec/benchmarks/" + projName)));

		IOpenable[] inputs = (IOpenable[]) folders.toArray(new IOpenable[1]);
		Analysis analysis = JqualPlugin.analyze(inputs, false);
		return analysis;
	}

	AnnotationStore getAnnotationStore(Analysis a) {
		AnnotationStore store = new AnnotationStore();
		storeAnnotations(store, a);
		return store;
	}

	public void storeAnnotations(AnnotationStore store, Analysis a) {
		for (MethodQType m : a.getOriginalMethodQTypes())
			store.put(m.methodQSig());
	}

	static void outputQualifierResults(String name, Analysis a) {
		JqualPlugin.out.println("Writing results to " + name);
		try {
			AnnotationWriter out = new AnnotationWriter(new PrintWriter(
					new FileWriter(name)));
			for (MethodQType m : a.getOriginalMethodQTypes()) {
				out.write(m.methodQSig());
			}
			for (FieldQSig f : a.getFieldQSigs()) {
				out.write(f);
			}
			out.flush();
		} catch (IOException e) {
			System.err.println("Exception writing qualifier results from "
					+ name + "\n" + e);
		}
	}

	public static void outputExperimentResults(String name, double kloc,
			Analysis a) {
		Result r = new Result(name, kloc, a);
		r.tableLine();

		JqualPlugin.printLog("missing_" + name, a.dumpMissingStuff());
		JqualPlugin.printlnLog("warningLog", a.toString());
	}

	Analysis callSpec(String projName, String suffix, Double kloc) {
		JqualPlugin.out.println("starting " + projName);
		Analysis a = spec(projName);
		outputExperimentResults(projName, kloc.doubleValue(), a);
		return a;
	}

	public void runFFIExperiments() {
		boolean subtypes = false;
		ffi("ffi_libglade-java-2.10.1", subtypes);
		ffi("ffi_libgtkhtml-java-2.6.0", subtypes);
		ffi("ffi_libreadline-java-0.8.0", subtypes);
		ffi("ffi_posix1.0", subtypes);
		ffi("ffi_jnetfilter", subtypes);
		ffi("ffi_libgnome-java-2.10.1", subtypes);
		ffi("ffi_libgtkmozembed-java-1.7.0", subtypes);
		ffi("ffi_libvte-java-0.11.11", subtypes);
		ffi("ffi_libgconf-java-2.10.1", subtypes);
		ffi("ffi_libgtk-java-2.6.2", subtypes);
		ffi("ffi_pgpjava", subtypes);
	}

	public AnnotationStore runJavariExperiments(String suffix) {
		JqualPlugin.inferReadonly = true;
		JqualPlugin.inferOpaque = false;

		JqualPlugin.out.println("starting jgap");
		Analysis a = jgap();
		outputExperimentResults("jgap", 10.199999999999999D, a);

		JqualPlugin.out.println("starting jgraph");
		a = jgraph();

		outputExperimentResults("jgraph", 11.9D, a);

		JqualPlugin.out.println("starting jtds");
		a = jtds();

		outputExperimentResults("jtds", 21.5D, a);

		callSpec("_202_jess", suffix, Double.valueOf(12.0D));
		callSpec("_200_check", suffix, Double.valueOf(7.0D));
		callSpec("_201_compress", suffix, Double.valueOf(6.2D));
		callSpec("_205_raytrace", suffix, Double.valueOf(7.7D));
		callSpec("_209_db", suffix, Double.valueOf(6.4D));
		callSpec("_213_javac", suffix, Double.valueOf(45.700000000000003D));

		callSpec("_227_mtrt", suffix, Double.valueOf(5.7D));

		JqualPlugin.out.println("starting jdbm");
		a = jdbm();
		outputExperimentResults("jdbm", 4.2D, a);

		JqualPlugin.out.println("starting jfreechart");
		a = jfreechart();
		outputExperimentResults("jfreechart", 121.40000000000001D, a);

		return null;
	}

	void CSFS() {
		JqualPlugin.contextSensitive = true;
		JqualPlugin.fieldSensitive = true;
		JqualPlugin.obeyFSAnnotations = false;
		JqualPlugin.allowMerge = false;
	}

	void CIFI() {
		JqualPlugin.contextSensitive = false;
		JqualPlugin.fieldSensitive = false;
		JqualPlugin.obeyFSAnnotations = false;
		JqualPlugin.allowMerge = false;
	}

	void CSFI() {
		JqualPlugin.contextSensitive = true;
		JqualPlugin.fieldSensitive = false;
		JqualPlugin.obeyFSAnnotations = false;
		JqualPlugin.allowMerge = false;
	}

	void CIFS() {
		JqualPlugin.contextSensitive = false;
		JqualPlugin.fieldSensitive = true;
		JqualPlugin.obeyFSAnnotations = false;
		JqualPlugin.allowMerge = false;
	}

	void CIFP() {
		JqualPlugin.contextSensitive = false;
		JqualPlugin.fieldSensitive = false;
		JqualPlugin.obeyFSAnnotations = true;
		JqualPlugin.allowMerge = false;
	}

	public static void CSFP() {
		JqualPlugin.contextSensitive = true;
		JqualPlugin.fieldSensitive = false;
		JqualPlugin.obeyFSAnnotations = true;
		JqualPlugin.allowMerge = false;
	}

	public void startTable(String title) {
		JqualPlugin.out.println("RESULTS_TABLE_START:" + title);
	}

	public void endTable() {
		JqualPlugin.out.println("RESULTS_TABLE_END");
	}

	public Object run(Object argsObj) {
		if (System.getProperty("commandLine") != null) {
			JqualPlugin.out.println("GOT CL");
			JqualPlugin.runFromCommandLine = true;
		}
		String expName = System.getProperty("experiment");
		if (expName != null) {
			JqualPlugin.out.println("Experiment: " + expName);
		}

		JqualPlugin.outputInEclipseConsole(false);

		JqualPlugin.banshee = false;
		JqualPlugin.limitFields = true;
		JqualPlugin.useLongNames = true;
		JqualPlugin.useClosure = false;

		JqualPlugin.useJavalib = false;
		JqualPlugin.debug = false;
		JqualPlugin.logConstraints = false;
		JqualPlugin.profile = false;
		JqualPlugin.inferInterned = false;
		JqualPlugin.logInferred = false;
		try {
			JqualPlugin.latticePath = this.rootPath + "jqual-tests/lattice";
			JqualPlugin.out.println("Lattice file is "
					+ JqualPlugin.latticePath);

			JqualPlugin.useClosure = true;

			JqualPlugin.useJavalib = true;
			JqualPlugin.logInferred = false;
			JqualPlugin.banshee = false;
			JqualPlugin.useJavariSelfLoopsHack = true;

			CIFI();
			JqualPlugin.out
					.println("running jgap to take care of start-up time");
			jgap();

			Analysis a = runJavariJdbm();
			outputQualifierResults("jqual_input", a);

			return null;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	void runJavariExperiment(String name) {
		JqualPlugin.inferReadonly = true;
		JqualPlugin.inferOpaque = false;
		JqualPlugin.out.println("starting " + name);
		Analysis a = null;
		try {
			Class cls = getClass();
			Class[] partypes = (Class[]) null;
			Object[] arglist = (Object[]) null;
			Method exp = null;
			if (name.contains("callSpec")) {
				partypes = new Class[3];
				partypes[0] = Class.forName("java.lang.String");
				partypes[1] = Class.forName("java.lang.String");
				partypes[2] = Class.forName("java.lang.Double");
				exp = cls.getDeclaredMethod("callSpec", partypes);
				String spectest = name.substring("callSpec.".length());
				arglist = new Object[] { spectest, "", new Double(0.0D) };
				JqualPlugin.out.println("Calling Spec with " + spectest);
			} else {
				partypes = new Class[0];
				arglist = new Object[0];
				exp = cls.getDeclaredMethod(name, partypes);
			}
			Experiments target = new Experiments();
			Object retobj = exp.invoke(target, arglist);
			a = (Analysis) retobj;
			outputExperimentResults(name, 0.0D, a);
		} catch (Throwable e) {
			System.err.println("Failed to run experiment: " + e);
			System.exit(1);
		}
	}

	AnnotationStore runJavariJustCheckit() {
		JqualPlugin.inferReadonly = true;
		JqualPlugin.inferOpaque = false;

		Analysis a = callSpec("_200_check", "", Double.valueOf(1.5D));
		return getAnnotationStore(a);
	}

	void runJavariJustFreeChart() {
		JqualPlugin.inferReadonly = true;
		JqualPlugin.inferOpaque = false;
		JqualPlugin.logInferred = true;
		JqualPlugin.out.println("starting jfreechart");
		Analysis a = jfreechart();
		outputExperimentResults("jfreechart", 121.40000000000001D, a);
	}

	void runJavariJgraph() {
		JqualPlugin.inferReadonly = true;
		JqualPlugin.inferOpaque = false;

		JqualPlugin.out.println("starting jgraph");
		Analysis a = jgraph();
		outputExperimentResults("jgraph", 11.9D, a);
	}

	void runJavariJgap() {
		CSFP();
		JqualPlugin.inferReadonly = true;
		JqualPlugin.inferOpaque = false;
		JqualPlugin.useClosure = true;

		JqualPlugin.useJavalib = true;

		JqualPlugin.out.println("starting jgap");
		Analysis a = jgap();
		outputExperimentResults("jgap", 11.9D, a);
	}

	Analysis runJavariJdbm() {
		CSFP();
		JqualPlugin.inferReadonly = true;
		JqualPlugin.inferOpaque = false;
		JqualPlugin.useClosure = true;

		JqualPlugin.useJavalib = true;
		JqualPlugin.logInferred = true;
		JqualPlugin.out.println("starting jdbm");
		Analysis a = jdbm();
		outputExperimentResults("jdbm", 11.9D, a);
		return a;
	}

	void runBansheeVsCqual() {
		JqualPlugin.inferReadonly = true;
		JqualPlugin.inferOpaque = false;

		JqualPlugin.banshee = false;

		JqualPlugin.noGates = true;

		JqualPlugin.out.println("starting jgap");
		Analysis a = jgap();

		JqualPlugin.out.println("starting jdbm");
		a = jdbm();
		outputExperimentResults("jdbm", 4.2D, a);

		JqualPlugin.noGates = false;
	}

	void runJavariFull() {
		JqualPlugin.inferReadonly = true;
		JqualPlugin.inferOpaque = false;
		JqualPlugin.banshee = false;
		JqualPlugin.noGates = false;
		JqualPlugin.logInferred = false;

		CIFI();
		startTable("CI, FI");
		runJavariExperiments("CIFI");
		endTable();

		CSFI();
		startTable("CS, FI");
		runJavariExperiments("CSFI");
		endTable();

		CSFP();
		startTable("CS, FP");
		runJavariExperiments("CSFP");
		endTable();
	}

	void compareFIFP() {
		JqualPlugin.inferReadonly = true;
		JqualPlugin.inferOpaque = false;

		CIFI();
		startTable("Javari Experiments: CI, FI");

		AnnotationStore FI = runJavariJustCheckit();
		endTable();

		CIFP();

		startTable("Javari Experiments: CI, FP");

		AnnotationStore FP = runJavariJustCheckit();
		endTable();

		JqualPlugin.out.println(FI.diff(FP));
	}

	void compareFull() {
		CSFI();
		startTable("Javari Experiments: CS, FI, no libs");
		AnnotationStore CSFIstore = runJavariExperiments("CSFI");
		endTable();

		CSFP();
		startTable("Javari Experiments: CS, FP, no libs");
		AnnotationStore CSFPstore = runJavariExperiments("CSFP");
		endTable();

		JqualPlugin.printlnLog("DIFF_RESULTS_FP_LT_FI",
				CSFIstore.diffLostPrecision(CSFPstore));
		JqualPlugin.printlnLog("DIFF_RESULTS_FI_LT_FP",
				CSFPstore.diffLostPrecision(CSFIstore));
	}

	AnnotationStore smallSpecExample() {
		IProject proj = this.root.getProject("/spec");
		List folders = new LinkedList();
		folders.add((IOpenable) JavaCore.create(proj
				.getFile("/spec/harness/Dialog2.java")));
		folders.add((IOpenable) JavaCore.create(proj
				.getFile("/spec/harness/QuickCalc.java")));
		folders.add((IOpenable) JavaCore.create(proj
				.getFile("/spec/harness/TuningNotes.java")));
		IOpenable[] inputs = (IOpenable[]) folders.toArray(new IOpenable[1]);
		Analysis analysis = JqualPlugin.analyze(inputs, false);
		return getAnnotationStore(analysis);
	}

	AnnotationStore smallFIFPExample() {
		IProject proj = this.root.getProject("/jqual-tests");
		List folders = new LinkedList();
		folders.add((IOpenable) JavaCore.create(proj
				.getFile("/javariTestSuite/FPFIDiffs.java")));
		IOpenable[] inputs = (IOpenable[]) folders.toArray(new IOpenable[1]);
		Analysis analysis = JqualPlugin.analyze(inputs, false);
		JqualPlugin.out.println(analysis);
		return getAnnotationStore(analysis);
	}

	@Override
	public Object start(IApplicationContext arg0) throws Exception {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void stop() {
		// TODO Auto-generated method stub

	}
}
